#include "slave_worker.h"

#define SLVPRINTF(rank, format, args...)  printf("[SLAVE WORKER %d] ", rank); printf(format, ##args); printf("\n")

/**
 * run cdcl algorithm on worker's cnf
 */
void run_cdcl(slaveworker* worker){
    assert(worker->rank > 0 && worker->rank < 32);
    SLVPRINTF(worker->rank, "try run CDCLalgorithm");
    dpllresult* res = CDCLalgorithm(worker->cdcl);
    if (res == NULL || res->sat == false){
        SLVPRINTF(worker->rank, "didn't got sat result");
        TODO();
    }
    else{
        SLVPRINTF(worker->rank, "found an sat result, sending it back");
        worker->cnf = res->model_cnf;
        slv_send_sat(worker);
    } 
}

 /**
 * check if we receive a stop message from master before the messages are delivered to master
 * if that's the case, cancel the message sends and return true
 * -> the caller is then responsible to * stop.
 */
_bool stop_received_before_message_completion(slaveworker* worker, MPI_Request* requests, int n_req){
    int flag = 0;
    int all_done = 0;
    int myid = worker->rank;

    SLVPRINTF(worker->rank, "request[0] %d; request[1] %d", requests[0], requests[1]);
    if (!worker->is_stop){
        while(flag == 0 && all_done == 0){
            MPI_Testall(n_req, requests, &all_done, MPI_STATUS_IGNORE);
            //只看不收
            MPI_Iprobe(0, 0, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE);
        }
    }
    //中途接到stop
    SLVPRINTF(myid, "finish send, alldone %d , flag %d", all_done, flag);
    if (flag != MPI_SUCCESS && !worker->is_stop){
        metadata meta;
        MPI_Recv(&meta, 1, mpi_metadata_t, 0, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        SLVPRINTF(myid, "recv something");

        if(meta.type != MSG_STOP_ALL && !worker->is_stop){
            printf("strange meta from master\n");
            assert(0);
        }
        
        worker->is_stop = true;
        if(!all_done){
            for(int i = 0; i < n_req; i++){
                MPI_Cancel(&requests[i]);
            }
        }
    }
    // flag == 0 => recv stop signal
    return flag != 0;
}
/**
 * !!!! this is a BLOCK function
 */
void block_wait_for_instructions_from_master(slaveworker* worker){
    metadata meta;
    
    SLVPRINTF(worker->rank, "waiting for inst from master");
    MPI_Status status;
    MPI_Recv(&meta, 1, mpi_metadata_t, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
    int rank = worker->rank;
    assert(status.MPI_SOURCE == 0);
    switch (meta.type){
    case MSG_STOP_ALL:
        //stop by master
        worker->is_stop = true;
        SLVPRINTF(rank, "stopped by master");
        break;
    case MSG_ASSIGN_CNF:
        //assign new cnf from master;
        worker->is_stop = false;
        if (meta.datalen == 0){
            if (!worker->inited && worker->cnf!=NULL){
                SLVPRINTF(worker->rank, "received signal from master, cnf can use now");
                cnf* record = worker->cnf;
                worker->cnf = new_cnf_from(worker->cnf);
                worker->cdcl->_cnf = worker->cnf;
                worker->cdcl->oricnf = worker->cnf;
                SLVPRINTF(rank, "inited cnf %p => %p(copy)",record, worker->cnf);
                print_cnf(worker->cnf);
                worker->inited = true;
            }

            //make sure cnf is not NULL
            if (worker->cnf == NULL){
                SLVPRINTF(rank, "receive NULL cnf, but self->cnf is also NULL");
                assert(0);
            }
            //start over, unset all the variables in cnf
            unset_all_vars(worker->cnf);

            run_cdcl(worker);
        }
        else{
            TODO();
        }
        break;
    default:
        SLVPRINTF(rank, "INVALID MSGTYPE FROM MASTER %d", meta.type);
        assert(0);
        break;
    };
}

/**
 * when reach an satisfiable result, use this function to 
 * send it to master
 */
void slv_send_sat(slaveworker* worker){
    print_cnf(worker->cnf);
    int myid = worker->rank;
    assert(worker->cnf != NULL);
    cnf* _cnf = worker->cnf;
    //if continue running, send the result
    if (!worker->is_stop){
        variable* vars = _cnf->var_set;
        for(int i = 0; i < _cnf->n_univar; i++){
            if(vars[i].assigned != 1){
                SLVPRINTF(myid, "var :%d is not assigned, set to true", vars[i].name);
                _set_variable_value(worker->cnf, vars[i].name, 1);
            }
        }
        unsigned num_assigned = count_assigned(vars, _cnf->n_univar);
        SLVPRINTF(myid, "calc num_assigned %d", num_assigned);
        MPI_Request requests[2];
        requests[0] = slv_send_metadata(MSG_FOUND_SOLUTION, num_assigned);
        requests[1] = slv_send_encoded_data(encode_variables(vars, _cnf->n_univar, num_assigned), num_assigned);

        _bool stop_receiving = stop_received_before_message_completion(worker, requests, 2);
        if (!stop_receiving){
            block_wait_for_instructions_from_master(worker);
        }
        else{
            printf("slave worker[%d] stopped by master\n", myid);
        }
    }
}

/**
 * send encoded unsigns to master
 */
MPI_Request slv_send_encoded_data(unsigned* encoded,  int len){
    MPI_Request request;
    printf("slv_send_encoded_data\n");
    MPI_Isend(encoded, len, MPI_UNSIGNED, 0, 0, MPI_COMM_WORLD, &request);
    return request;
}

/**
 * send meta data to prepare sending encoded data
 */
MPI_Request slv_send_metadata(int t, unsigned len){
    metadata meta;
    meta.type = t;
    meta.datalen = len;
    printf("slv_send_metadata\n");

    MPI_Request request;
    MPI_Isend(&meta, 1, mpi_metadata_t, 0, 0, MPI_COMM_WORLD, &request);
    return request;
}

void init_worker(slaveworker* worker, int rank, cnf* _cnf){
    printf("initing worker %p, rank %d\n", worker, rank);
    assert(worker != NULL);
    if (rank > 32 || rank < 1){
        printf("rank should  < 32 and > 0\n");
        assert(0);
    }
    worker->rank = rank;
    worker->inited = false;

    //init cecl
    worker->cdcl = malloc(sizeof(cdcl));
    worker->cdcl->rank = rank;
    worker->cdcl->parentdecision = NULL;
    worker->cdcl->oricnf = NULL;
    worker->cdcl->config = NULL;
    cdcl_init(worker->cdcl);
    worker->cnf = _cnf;//new_cnf_from(_cnf);    
    SLVPRINTF(worker->rank, "very original cnf: %p", worker->cnf);
}

void update_config(slaveworker* worker, const cdcl_config* config){
    worker->config.var_iter = config->var_iter;
    worker->config.restart_round = config->restart_round;
    worker->cdcl->config = &worker->config;
    SLVPRINTF(worker->rank, "update config %d, %d", worker->cdcl->config->var_iter, worker->cdcl->config->restart_round);
}